﻿using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;

using LeanEngine.Extensions;
using LeanEngine.Entity;
using LeanEngine.Data;
using LeanEngine.Data.Linq;
using LeanEngine.Collections;
using LeanEngine.Business.Security.Entity;

namespace LeanEngine.Business.Security.Data
{
    /// <summary>
    /// Represents a data access object for the security repository.
    /// </summary>
    internal sealed class SecurityRepository : ISecurityRepository
    {
        #region Singleton Members

        private static SecurityRepository instance = new SecurityRepository();

        public static SecurityRepository Instance { get { return instance; } }

        private SecurityRepository() { }

        #endregion

        #region Method Members

        /// <summary>
        /// Gets the membership that has the specified user name and password.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="userName">The user name of the membership.</param>
        /// <param name="password">The passowrd of the membership.</param>
        public Membership GetMembership(string userName, string password)
        {
            if (userName.IsNullOrEmpty()) throw new ArgumentNullException("userName");
            if (password.IsNullOrEmpty()) throw new ArgumentNullException("password");

            using (var db = new DatabaseContext())
            {
                var result = from m in db.GetTable<Membership>()
                             where m.UserName == userName &&
                                    m.Password == password
                             select m;
                var resultList = result.ToList();
                return resultList.FirstOrDefault();
            }
        }

        /// <summary>
        /// Gets the user profile that has the specified name.
        /// </summary>
        /// <exception cref="ArgumentNullException"></exception>
        /// <param name="name">The name of the application method.</param>
        public UserProfile GetProfile(string name)
        {
            if (name.IsNullOrEmpty()) throw new ArgumentNullException("name");

            using (var db = new DatabaseContext())
            {
                var result = from m in db.GetTable<UserProfile>()
                             where m.Name == name
                             select m;
                var resultList = result.ToList();
                return resultList.FirstOrDefault();
            }
        }

        /// <summary>
        /// Gets the application method that has the specified name.
        /// </summary>
        /// <exception cref="ArgumentNullException"></exception>
        /// <param name="name">The name of the application method.</param>
        public ApplicationMethod GetApplicationMethod(string name)
        {
            if (name.IsNullOrEmpty()) throw new ArgumentNullException("name");

            using (var db = new DatabaseContext())
            {
                var result = from m in db.GetTable<ApplicationMethod>()
                             where m.Name == name
                             select m;
                var resultList = result.ToList();
                return resultList.FirstOrDefault();
            }
        }

        /// <summary>
        /// Evaluate whether a membership has or not grant access to a method
        /// </summary>
        /// <param name="methodName">The name of the application method.</param>
        /// <param name="userName">The user name of the membership.</param>
        /// <param name="password">The passowrd of the membership.</param>
        public bool HasAccess(string methodName, string userName, string password)
        {
            if (userName.IsNullOrEmpty()) throw new ArgumentNullException("userName");
            if (password.IsNullOrEmpty()) throw new ArgumentNullException("password");
            if (methodName.IsNullOrEmpty()) throw new ArgumentNullException("methodName");

            using (var db = new DatabaseContext())
            {
                var exceptionList = (from e in db.GetTable<ExceptionPolicy>()
                                    join u in db.GetTable<Membership>() on e.Membership equals u
                                    join m in db.GetTable<ApplicationMethod>() on e.Method equals m
                                    where u.UserName == userName &&
                                        u.Password == password &&
                                        m.Name == methodName
                                    select e).ToList();

                if (exceptionList.Count == 1)
                {
                    return exceptionList[0].AllowedAccess;
                }
                else
                {
                    var membershipCount = (from u in db.GetTable<Membership>()
                                           join p in db.GetTable<UserProfile>() on u.ProfilesList.AsJoinRelation() equals p
                                           join m in db.GetTable<ApplicationMethod>() on p.ApplicationMethods.AsJoinRelation() equals m
                                           where u.UserName == userName &&
                                               u.Password == password &&
                                               m.Name == methodName
                                           select u).Count();

                    return membershipCount > 0;
                }
            }
        }

        #endregion
    }
}
